package main

import (
	"code.google.com/p/marius/simplenote"
	"code.google.com/p/marius/simplenote/db"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"regexp"
	"sort"
	"strconv"
	"time"
)

var dbpath *string = flag.String("f", os.Getenv("HOME")+"/.notedb", "Path to db file")

type indexedNote struct {
	*simplenote.Note
	index int
}

type byModTime []*indexedNote

// flagsets per command, etc.

func (notes byModTime) Len() int {
	return len(notes)
}

func (notes byModTime) Less(i, j int) bool {
	return notes[i].ModTime().After(notes[j].ModTime())
}

func (notes byModTime) Swap(i, j int) {
	tmp := notes[i]
	notes[i] = notes[j]
	notes[j] = tmp
}

func getArg(args []string, db *db.Db) *simplenote.Note {
	if len(args) < 2 {
		fmt.Fprintf(os.Stderr, "No note number\n")
		os.Exit(1)
	}

	i, err := strconv.Atoi(args[1])
	if err != nil {
		log.Fatal(err)
	}
	notes := db.Notes()
	if i < 0 || len(notes) <= i {
		fmt.Fprintf(os.Stderr, "Out of range\n")
		os.Exit(1)
	}

	return notes[i]
}

func edit(body string) string {
	file, err := ioutil.TempFile("/tmp", "note")
	if err != nil {
		log.Fatal(err)
	}
	defer os.Remove(file.Name())
	defer file.Close()

	if _, err := file.WriteString(body); err != nil {
		log.Fatal(err)
	}

	editor := os.Getenv("EDITOR")
	if editor == "" {
		log.Fatal("No EDITOR set")
	}
	editor, err = exec.LookPath(editor)
	if err != nil {
		log.Fatal(err)
	}

	proc, err := os.StartProcess(editor, []string{editor, file.Name()}, &os.ProcAttr{})
	if err != nil {
		log.Fatal(err)
	}

	st, err := proc.Wait()
	if err != nil {
		log.Fatal(err)
	}
	if !st.Success() {
		log.Fatal("process failed")
	}

	proc.Release()

	// 	cmd := exec.Command(editor, file.Name())
	// 	out, err := cmd.CombinedOutput()
	// 	fmt.Fprintf(os.Stderr, string(out))
	// 	if err != nil {
	// 		log.Fatal(err)
	//	}
	// 	if err := cmd.Run(); err != nil {
	// 		log.Fatal(err)
	// 	}

	bytes, err := ioutil.ReadFile(file.Name())
	if err != nil {
		log.Fatal(err)
	}

	return string(bytes)
}

func notesByMod(db *db.Db) []*indexedNote {
	notes := make([]*indexedNote, 0)
	for i, note := range db.Notes() {
		if note.Deleted != 0 {
			continue
		}
		notes = append(notes, &indexedNote{note, i})
	}

	sort.Sort(byModTime(notes)) // (reverse)
	return notes
}

// commands: with their own flag sets, etc.

func main() {
	flag.Parse()
	args := flag.Args()

	if len(args) < 1 {
		fmt.Fprintf(os.Stderr, "No command provided\n")
		os.Exit(1)
	}

	db, err := db.Open(*dbpath)
	if err != nil {
		log.Fatal(err)
	}

	if len(args) == 0 {
		if _, err := db.Sync(); err != nil {
			log.Fatal(err)
		}
		os.Exit(0)
	}

	switch args[0] {
	case "ls":
		notes := notesByMod(db)

		for _, note := range notes {
			title, _ := note.TitleBody()
			if note.Deleted != 0 {
				continue
			}
			fmt.Printf("%d/ %s\n", note.index, title)
		}

	case "grep":
		// TODO: -i, -v
		// TODO: show matching parts -- in lines, just like grep does.
		//
		//	45/  line line line line...
		// just do match per line?
		if len(args) < 2 {
			fmt.Fprintf(os.Stderr, "no query")
			os.Exit(1)
		}

		notes := notesByMod(db)
		for _, note := range notes {
			if m, err := regexp.MatchString(args[1], note.Content); err != nil || !m {
				continue
			}
			title, _ := note.TitleBody()
			fmt.Printf("%d/ %s\n", note.index, title)
		}

	case "edit":
		note := getArg(args, db)
		note.Content = edit(note.Content)
		note.Modifydate = fmt.Sprintf("%d.0", time.Now().Unix())
		if _, err := db.Save(note); err != nil {
			log.Fatal(err)
		}

	case "new":
		body := edit("")
		if body == "" {
			return
		}
		note := &simplenote.Note{Content: body, Modifydate: fmt.Sprintf("%d.0", time.Now().Unix())}
		if _, err := db.Save(note); err != nil {
			log.Fatal(err)
		}

	case "cat":
		note := getArg(args, db)
		os.Stdout.WriteString(note.Content)

	case "rm":
		note := getArg(args, db)
		note.Deleted = 1
		if _, err := db.Save(note); err != nil {
			log.Fatal(err)
		}

	case "debug":
		note := getArg(args, db)
		fmt.Println(db.DebugString(note))
		
	case "sync":
		if _, err := db.Sync(); err != nil {
			log.Fatal(err)
		}


	default:
		fmt.Fprintf(os.Stderr, "Invalid command: %s\n", args[0])
		os.Exit(1)
	}
}
